package de.jmda.gen.java.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.validation.constraints.NotNull;

import org.apache.commons.lang.StringUtils;

import de.jmda.gen.Generator;
import de.jmda.gen.GeneratorException;
import de.jmda.gen.impl.AbstractCompoundGenerator;
import de.jmda.gen.java.CompilationUnitTypeGenerator;
import de.jmda.gen.java.ImportStatementGenerator;
import de.jmda.gen.java.ImportStatementsGenerator;
import de.jmda.gen.java.PackageStatementGenerator;
import de.jmda.gen.java.TypeGenerator;
import de.jmda.gen.java.TypeModifiersGenerator;
import de.jmda.gen.java.ModifiersUtil.VisibilityModifier;

/**
 * @author roger.jmda@gmail.com
 */
public class DefaultCompilationUnitTypeGenerator
		extends AbstractCompoundGenerator
		implements CompilationUnitTypeGenerator
{
	public final static String DEFAULT_TYPE_NAME = "DEFAULT_TYPE_NAME";

	private PackageStatementGenerator packageStatementGenerator;
	private ImportStatementsGenerator importStatementsGenerator;

	@NotNull
	private TypeGenerator compilationUnitTypeDeclarationGenerator;
	
	@NotNull
	private List<TypeGenerator> topLevelTypeDeclarationGenerators =
			new ArrayList<TypeGenerator>();

	public DefaultCompilationUnitTypeGenerator()
  {
		this(null, null, null, null);
  }

	public DefaultCompilationUnitTypeGenerator(
      PackageStatementGenerator packageStatementGenerator,
      ImportStatementsGenerator importStatementsGenerator,
      TypeGenerator compilationUnitTypeDeclarationGenerator,
      List<TypeGenerator> topLevelTypeDeclarationGenerators)
  {
	  super();

	  setPackageStatementGenerator(packageStatementGenerator);
	  setImportStatementsGenerator(importStatementsGenerator);
	  setCompilationUnitTypeDeclarationGenerator(compilationUnitTypeDeclarationGenerator);
	  setTopLevelTypeDeclarationGenerators(topLevelTypeDeclarationGenerators);

	  setComponentSeparator(DOUBLE_LINE_SEPARATOR);
  }

	/**
	 * Returns collection of <code>Generator</code>s with the return values of the
	 * following methods in the following order:
	 * <ul>
	 *   <li>{@link #getPackageStatementGenerator()},</li>
	 *   <li>{@link #getImportStatementsGenerator()}</li>
	 *   <li>{@link #getCompilationUnitTypeDeclarationGenerator()} and</li>
	 *   <li>all the generators from {@link
	 *       #getTopLevelTypeDeclarationGenerators()}</li>
	 * </ul>
	 * 
	 * @see de.jmda.gen.CompoundGenerator#getGeneratorComponents()
	 */
	@Override
  public List<Generator> getGeneratorComponents()
  {
		List<Generator> result = new ArrayList<Generator>();

		result.add(getPackageStatementGenerator());
		result.add(getImportStatementsGenerator());
		result.add(getCompilationUnitTypeDeclarationGenerator());

		for (TypeGenerator typeDeclarationGenerator :
		     getTopLevelTypeDeclarationGenerators())
		{
			result.add(typeDeclarationGenerator);
		}

		return result;
  }

	/**
	 * Works like {@link AbstractCompoundGenerator#generate()} with the following
	 * difference: generation of import statements will be postponed until all
	 * other generators have generated their parts.
	 *
	 * @see de.jmda.gen.impl.AbstractCompoundGenerator#generate()
	 */
	@Override
  public StringBuffer generate() throws GeneratorException
  {
		Collection<StringBuffer> partsPreImportStatements =
				new ArrayList<StringBuffer>();
		Collection<StringBuffer> partsPostImportStatements =
				new ArrayList<StringBuffer>();
		boolean foundImportStatementsGenerator = false;

		for (Generator generator : getGeneratorComponents())
		{
			// maybe a generator is deactivated by being set to null
			if (generator != null)
			{
				if (generator == getImportStatementsGenerator())
				{
					foundImportStatementsGenerator = true;
				}
				else
				{
					StringBuffer part = generator.generate();

					// add only non null and non empty parts
					if (part != null)
					{
						if (part.length() > 0)
						{
							if (foundImportStatementsGenerator == false)
							{
								partsPreImportStatements.add(part);
							}
							else
							{
								partsPostImportStatements.add(part);
							}
						}
					}
				}
			}
		}

		Collection<StringBuffer> parts = new ArrayList<>();
		
		for (StringBuffer part : partsPreImportStatements)
		{
			parts.add(part);
		}

		ImportStatementsGenerator importStatementsGenerator =
				getImportStatementsGenerator();

		if (importStatementsGenerator != null)
		{
			StringBuffer importStatementsPart = importStatementsGenerator.generate();

			// add only non null and non empty parts
			if (importStatementsPart != null)
			{
				if (importStatementsPart.length() > 0)
				{
					parts.add(importStatementsPart);
				}
			}
		}

		for (StringBuffer part : partsPostImportStatements)
		{
			parts.add(part);
		}

		return
				getLineIndenter().indent(
						new StringBuffer(StringUtils.join(parts, getComponentSeparator())));
  }

	/**
	 * @return {@link #packageStatementGenerator}, may be <code>null</code>
	 *         because a package statement is optional.
	 *
	 * @see de.jmda.gen.java.CompilationUnitTypeGenerator#getPackageStatementGenerator()
	 */
	@Override
  public PackageStatementGenerator getPackageStatementGenerator()
  {
	  return packageStatementGenerator;
  }

	/**
	 * @see de.jmda.gen.java.CompilationUnitTypeGenerator#setPackageStatementGenerator(de.jmda.gen.java.PackageStatementGenerator)
	 */
	@Override
  public void setPackageStatementGenerator(PackageStatementGenerator generator)
  {
		packageStatementGenerator = generator;
  }

	/**
	 * @return {@link #importStatementsGenerator}, may be <code>null</code>
	 *         because import statements are optional.
	 *
	 * @see de.jmda.gen.java.CompilationUnitTypeGenerator#getImportStatementsGenerator()
	 */
	@Override
  public ImportStatementsGenerator getImportStatementsGenerator()
  {
	  return importStatementsGenerator;
  }

	/**
	 * @see de.jmda.gen.java.CompilationUnitTypeGenerator#setImportStatementsGenerator(de.jmda.gen.java.ImportStatementsGenerator)
	 */
	@Override
  public void setImportStatementsGenerator(ImportStatementsGenerator generator)
  {
		importStatementsGenerator = generator;
  }

	/**
	 * @return {@link #compilationUnitTypeDeclarationGenerator}, if type generator
	 *         is <code>null</code> it will be lazily initialised because a public
	 *         top level type declaration is mandatory.
	 *
	 * @see de.jmda.gen.java.CompilationUnitTypeGenerator#getCompilationUnitTypeDeclarationGenerator()
	 */
	@Override
	@NotNull
  public TypeGenerator getCompilationUnitTypeDeclarationGenerator()
  {
	  return notNull(compilationUnitTypeDeclarationGenerator);
  }

	@Override
  public void setCompilationUnitTypeDeclarationGenerator(
  		@NotNull TypeGenerator generator)
  {
		compilationUnitTypeDeclarationGenerator = notNull(generator);
  }

	/**
	 * @see de.jmda.gen.java.CompilationUnitTypeGenerator#setPackageName(java.lang.String)
	 */
	@Override
  public void setPackageName(String packagename)
  {
		if (packagename == null)
		{
			setPackageStatementGenerator(null);
		}
		else
		{
			demandPackageStatementGenerator().setPackageName(packagename);
		}
  }

	@Override
	public List<TypeGenerator> getTopLevelTypeDeclarationGenerators()
	{
		return topLevelTypeDeclarationGenerators;
	}

	@Override
	public void setTopLevelTypeDeclarationGenerators(
	    @NotNull List<TypeGenerator> generators)
	{
		topLevelTypeDeclarationGenerators = notNull(generators);
	}

	@Override
  @NotNull
  public String getCompilationUnitTypeName() throws GeneratorException
  {
		return
				getCompilationUnitTypeDeclarationGenerator().getDeclaredTypeGenerator()
				                                            .getTypeName();
  }

	@Override
  public void setCompilationUnitTypeName(@NotNull String typeName)
  {
		getCompilationUnitTypeDeclarationGenerator().getDeclaredTypeGenerator()
		                                            .setTypeName(typeName);
  }

	@Override
  public PackageStatementGenerator demandPackageStatementGenerator()
  {
		packageStatementGenerator = notNull(packageStatementGenerator);
	  return packageStatementGenerator;
  }

	@Override
  public ImportStatementsGenerator demandImportStatementsGenerator()
  {
		importStatementsGenerator = notNull(importStatementsGenerator);
	  return importStatementsGenerator;
  }

	private PackageStatementGenerator notNull(PackageStatementGenerator generator)
  {
		if (generator == null)
		{
			generator = new DefaultPackageStatementGenerator();
		}

		return generator;
  }

	private ImportStatementsGenerator notNull(ImportStatementsGenerator generator)
  {
		if (generator == null)
		{
			generator = new DefaultImportStatementsGenerator();
		}

		return generator;
  }

	private TypeGenerator notNull(TypeGenerator generator)
  {
		if (generator == null)
		{
			generator = new DefaultTypeGenerator();
		}

		return generator;
  }

	private List<TypeGenerator> notNull(
			List<TypeGenerator> generators)
  {
		if (generators == null)
		{
			generators = new ArrayList<TypeGenerator>();
		}
		
		return generators;
  }

	@Override
  public void setVisibility(VisibilityModifier modifier)
  {
		TypeModifiersGenerator modifiersGenerator =
				getCompilationUnitTypeDeclarationGenerator()
						.getDeclaredTypeGenerator()
						.demandModifiersGenerator();
		
		if (modifier == VisibilityModifier.DEFAULT)
		{
			// do nothing
		}
		else if (modifier == VisibilityModifier.PRIVATE)
		{
			modifiersGenerator.setPrivate(true);
		}
		else if (modifier == VisibilityModifier.PROTECTED)
		{
			modifiersGenerator.setProtected(true);
		}
		else if (modifier == VisibilityModifier.PUBLIC)
		{
			modifiersGenerator.setPublic(true);
		}
  }

	@Override
  public void addImportStatementGenerators(
      List<ImportStatementGenerator> generators)
  {
		demandImportStatementsGenerator().getImportStatementGenerators()
		                                 .addAll(generators);
  }
}